package com.dhy.json2module;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

import java.util.*;
import java.util.Map.Entry;

/**
 * set json to the json.txt file in the parent folder of 'src'
 */
public class Json2Module {
    public static void main(String[] args) {
        Json2Module util = new Json2Module();
        String json = ClipboardUtil.getText();
        System.out.println("Clipboard get json length:" + json.length());
        util.startCheckPath("TestData", json);
    }

    private Map<String, Map<String, String>> textClassData = new HashMap<>();

    private void startCheckPath(String className, String jsonString) {
        JSONObject main = null;
        jsonString = jsonString.trim();
        try {
            if (jsonString.startsWith("[")) {
                checkPath(className, JSONArray.fromObject(jsonString));
            } else {
                checkPath(className, main = JSONObject.fromObject(jsonString));
            }
        } catch (Exception e) {
            System.out.println("Parse json to java module failed\n"
                    + "please check input json");
            return;
        }

        nameJsonMap.entrySet().stream().filter(d -> d.getValue() instanceof JSONObject).forEach(
                d -> textClassData.put(d.getKey(), createTextClass(d.getKey(), (JSONObject) d.getValue()))
        );
        print(main, className);
    }

    private void print(JSONObject main, String className) {
        StringBuffer sb = new StringBuffer();
//        sb.append("public class ").append(className).append("{\n");
        printByOrder(sb, main, textClassData.remove(className));
        Map<Map<String, String>, String> check = new HashMap<>();
        List<String> list = new ArrayList<>();
        for (Entry<String, Map<String, String>> d : textClassData.entrySet()) {
            if (!check.containsKey(d.getValue())) {
                check.put(d.getValue(), d.getKey());
                print(sb, d);
            } else {// repeated class
                list.add(d.getKey() + "," + check.get(d.getValue()));
            }
        }
        String out = sb.toString();//.append("}")
        for (String d : list) {
            String oldNew[] = d.split(",");
            out = out.replaceAll(oldNew[0], oldNew[1]);
            textClassData.remove(oldNew[0]);
        }
        list.clear();
        customClassNames.addAll(textClassData.keySet());
        clearRepeatedName();
        sortClassName();
        for (String name : customClassNames) {
            String n = getSimpleClassName(list, name);
            out = out.replaceAll(name, n);
        }
        ClipboardUtil.setText(out);
        System.out.println("Parse json to java module success\n" +
                "Result has been copy to Clipboard\n" +
                "Result length:" + out.length());
    }

    private void clearRepeatedName() {
        Map<String, String> data = new HashMap<>();
        for (String k : customClassNames) {
            data.put(k, null);
        }
        customClassNames.clear();
        customClassNames.addAll(data.keySet());
    }

    private void sortClassName() {
        customClassNames.sort((f, s1) -> f.length() > s1.length() ? -1 : 1);
    }

    private int newClassCount;

    private String getSimpleClassName(List<String> list, String name) {
        if (name.endsWith(".")) {//new name
            return getSimpleClassName(list, name.substring(0, name.length() - 1));
        } else {
            boolean repeated = false;
            String n;
            do {
                n = name.substring(name.lastIndexOf(".") + 1);
                n = getUpperName(n);
                if (repeated) {
                    n += (++newClassCount);
                }
                repeated = true;
            } while (list.contains(n.toLowerCase()));
            list.add(n.toLowerCase());
            return n;
        }
    }

    private void printByOrder(StringBuffer sb, JSONObject j, Map<String, String> data) {
        Iterator<String> keyIter = j.keys();
        while (keyIter.hasNext()) {
            String key = keyIter.next();
            print(sb, key, data.remove(key));
        }
    }

    private void print(StringBuffer sb, String className, Map<String, String> data) {
        sb.append("public class ").append(className).append("{\n");
        for (Entry<String, String> d : data.entrySet()) {
            print(sb, d.getKey(), d.getValue());
        }
        if (data.isEmpty()) {
            sb.append("//FIXME").append("\n");
        }
        sb.append("}\n");
    }

    private void print(StringBuffer sb, String key, String type) {
        sb.append("public ").append(type).append(" ").append(key).append(";");
        sb.append(type.contains("?") ? "//FIXME" : "").append("\n");
    }

    private void print(StringBuffer sb, Entry<String, Map<String, String>> entry) {
        print(sb, entry.getKey(), entry.getValue());
    }

    private Map<String, String> createTextClass(String className, JSONObject jsonObject) {
        Map<String, String> p = new HashMap<>();
        if (!hasData(jsonObject)) {
            jsonObject = (JSONObject) nameJsonMap.get(className);
        }
        Iterator<String> keyIter = jsonObject.keys();
        while (keyIter.hasNext()) {
            String type, member = keyIter.next();
            Object value = jsonObject.get(member);
            if (value instanceof Integer) {
                type = "int";
            } else if (value instanceof Boolean) {
                type = "boolean";
            } else if (value instanceof String) {
                type = "String";
            } else if (value instanceof Double) {
                type = "double";
            } else if (value instanceof JSONObject) {
                type = className + "." + member;
            } else if (value instanceof JSONArray) {
                type = getJsonArrayType(className + "." + member, value);
            } else {
                type = "UNKNOWN";
            }
            p.put(member, type);
        }
        return p;
    }

    private String getJsonArrayType(String className, Object value) {
        String type;
        JSONArray a = (JSONArray) value;
        if (!hasData(a)) {
            a = (JSONArray) nameJsonMap.get(className);
        }
        if (hasData(a)) {
            value = a.get(0);
            if (value instanceof String) {
                type = "List<String>";
            } else if (value instanceof Integer) {
                type = "List<Intger>";
            } else if (value instanceof Boolean) {
                type = "List<Boolean>";
            } else if (value instanceof JSONObject) {
                className += ".";
                type = "List<" + className + ">";
            } else {// JSONArray
                type = "List<" + getJsonArrayType(className + ".", a) + ">";
            }
        } else {
            type = "List<?>";
        }
        return type;
    }

    private List<String> customClassNames = new ArrayList<>();

    private static String getUpperName(String name) {
        if (name.startsWith("_")) {
            name = name.substring(1);
        }
        return name.substring(0, 1).toUpperCase() + name.substring(1);
    }

    private Map<String, Object> nameJsonMap = new HashMap<>();

    private void checkPath(String className, JSONArray jsonCalss) {
        JSONArray old = (JSONArray) nameJsonMap.get(className);
        if (!hasData(old)) {
            nameJsonMap.put(className, jsonCalss);
        }
        for (Object value : jsonCalss) {
            if (value instanceof JSONObject) {
                checkPath(className + ".", (JSONObject) value);
            } else if (value instanceof JSONArray) {
                checkPath(className + ".", (JSONArray) value);
            }
        }
    }

    private void checkPath(String className, JSONObject jsonCalss) {
        JSONObject old = (JSONObject) nameJsonMap.get(className);
        if (!hasData(old)) {
            nameJsonMap.put(className, jsonCalss);
        }
        Iterator<String> i = jsonCalss.keys();
        while (i.hasNext()) {
            String member = i.next();
            Object value = jsonCalss.get(member);
            if (value instanceof JSONObject) {
                checkPath(className + "." + member, (JSONObject) value);
            } else if (value instanceof JSONArray) {
                checkPath(className + "." + member, (JSONArray) value);
            }
        }
    }

    private boolean hasData(JSONObject jsonCalss) {
        return !(jsonCalss == null || jsonCalss.isNullObject() || jsonCalss.isEmpty());
    }

    private boolean hasData(JSONArray jsonCalss) {
        return !(jsonCalss == null || jsonCalss.isEmpty());
    }
}
